#include "Task.hpp"
#include <vector>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;


const int tasks_num = 10;
vector<task_t> tasks;

//描述管道属性
class channel
{
public:
    channel(int cmdfd, pid_t slaverid, string processname)
        :_cmdfd(cmdfd),
        _slaverid(slaverid),
        _processname(processname)
    {}

public:
    int _cmdfd;     // 发送任务的文件描述符(后续write发送任务的时候只需要channel对象即可，read已经设置了从0号文件读)
    pid_t _slaverid;     // 子进程ID
    string _processname;     // 子进程名称
};      //结构与类后面需要加分好：；



void slaver()
{
    while (true)
    {
        int cmdcode = 0;
        int ret = read(0, &cmdcode, sizeof(cmdcode));   //ssize_t read(int fd, void *buf, size_t count);
        if (ret == 4)
        {
            std::cout <<"slaver say@ get a command: "<< getpid() << " : cmdcode: " <<  cmdcode << std::endl;
            if(cmdcode >= 0 && cmdcode < tasks.size()) tasks[cmdcode]();    //tasks内部数据是函数指针
        }
        else if (ret == 0)
            break;  //跳出循环
    }
}

// 输入：const &
// 输出：*
// 输入输出：&

void InitProPool(vector<channel>* channels)
{
    // version 2: 确保每一个子进程都只有一个写端
    std::vector<int> oldfds;
    for (int i = 0; i < tasks_num; i++)
    {
        //创建管道
        int pipefd[2] = {0};
        int ret = pipe(pipefd);
        if (ret < 0) return;

        //创建子进程
        pid_t id = fork();
        /*
        当父进程调用fork()时，以下步骤会发生：
        操作系统创建一个新的进程，这个新进程称为子进程。
        子进程获得父进程的代码段、数据段、堆、栈等资源的副本（注意是副本，但是这些资源在物理内存中是共享的，
        直到任一进程尝试写入时发生写时复制）。
        fork()在父进程中返回子进程的进程ID，在子进程中返回0。

        也就是说子进程会获得副本中的数据，同时执行fork下面的if（ret == 0）内部的代码。
        */
        if (id == 0)     //子进程
        {
            std::cout << "child: " << getpid() << " close history fd: ";
            for(auto fd : oldfds) {          //oldfds存放的是写端，子文件需要关闭！所有的！写端
                std::cout << fd << " ";
                close(fd);
            }
            std::cout << "\n";

            close(pipefd[1]);
            dup2(pipefd[0], 0);  // 0:标准输入
            close(pipefd[0]);
            slaver();
            std::cout << "process : " << getpid() << " quit" << std::endl;

            exit(0);
        }

        //父进程
        oldfds.push_back(pipefd[1]);    //父进程开辟出写端之后，将写端push_back
        close(pipefd[0]);

        std::string name = "process-" + std::to_string(i);
        channels->push_back(channel(pipefd[1], id, name));  //匿名对象传参  //一定要在父进程去初始化channel对象，因为channel对象需要子进程的pid
        sleep(1);   
    }
}

void Debug(const std::vector<channel> &channels)
{
    // test
    for(const auto &c :channels)
    {
        std::cout << c._cmdfd << " " << c._slaverid << " " << c._processname << std::endl;
    }
}


void Menu()
{
    std::cout << "################################################" << std::endl;
    std::cout << "# 1. 刷新日志             2. 刷新出来野怪        #" << std::endl;
    std::cout << "# 3. 检测软件是否更新      4. 更新用的血量和蓝量  #" << std::endl;
    std::cout << "#                         0. 退出               #" << std::endl;
    std::cout << "#################################################" << std::endl;
}

void CtrlSlaver(const std::vector<channel> &channels)
{
    int which = 0;
    while (true)
    {
        //选择任务
        int select = 0; 
        Menu();
        std::cout << "Please Enter@ ";
        std::cin >> select;
        if(select <= 0 || select >= 5) break;

        int cmdcode = select - 1;


        //选择进程：1.轮询 2.随机    
    // int processpos = rand()%channels.size();
        std::cout << "father say: " << " cmdcode: " <<
        cmdcode << " already sendto " << channels[which]._slaverid << " process name: " 
        << channels[which]._processname<< std::endl;


        // 3. 发送任务
        write(channels[which]._cmdfd, &cmdcode, sizeof(cmdcode));   //一旦写入之后，被阻塞的子进程尝试启动  //每一个子进程都一个写端的wfd
        which++;
        which %= channels.size();
    }
}

void QuitProcess(const std::vector<channel> &channels)
{
     for(const auto &c : channels){     //close与wait一起进行。但是在子进程中，需要close（oldWfds）
        close(c._cmdfd);
        waitpid(c._slaverid, nullptr, 0);
    }
    // version1 
    // int last = channels.size()-1;
    // for(int i = last; i >= 0; i--)       //倒着进行
    // {
    //     close(channels[i]._cmdfd);
    //     waitpid(channels[i]._slaverid, nullptr, 0);
    // }

    // for(const auto &c : channels) close(c._cmdfd);   //先close再wait
    // // sleep(5);
    // for(const auto &c : channels) waitpid(c._slaverid, nullptr, 0);
    // // sleep(5);
}



int main()
{

    LoadTask(&tasks);    
    srand(time(nullptr)^getpid()^1023); // 种一个随机数种子

    //1.获得子进程与管道
    vector<channel> channels;
    InitProPool(&channels);

    //2.创建任务，写入数据
    CtrlSlaver(channels);

    //3.资源清理
    QuitProcess(channels);

    return 0;
}
